import * as Inula from 'openinula'

const inulaAct = typeof Inula.act === 'function' ? Inula.act : null

function getGlobalThis() {
  if (typeof globalThis !== 'undefined') {
    return globalThis
  }
  if (typeof self !== 'undefined') {
    return self
  }
  if (typeof window !== 'undefined') {
    return window
  }
  if (typeof global !== 'undefined') {
    return global
  }
  throw new Error('unable to locate global object')
}

function setIsInulaActEnvironment(isInulaActEnvironment) {
  getGlobalThis().IS_INULA_ACT_ENVIRONMENT = isInulaActEnvironment
}

function getIsInulaActEnvironment() {
  return getGlobalThis().IS_INULA_ACT_ENVIRONMENT
}

function withGlobalActEnvironment(actImplementation) {
  return callback => {
    const previousActEnvironment = getIsInulaActEnvironment()
    setIsInulaActEnvironment(true)
    try {
      let callbackNeedsToBeAwaited = false
      const actResult = actImplementation(() => {
        const result = callback()
        if (
          result !== null &&
          typeof result === 'object' &&
          typeof result.then === 'function'
        ) {
          callbackNeedsToBeAwaited = true
        }
        return result
      })
      if (callbackNeedsToBeAwaited) {
        const thenable = actResult
        return {
          then: (resolve, reject) => {
            thenable.then(
              returnValue => {
                setIsInulaActEnvironment(previousActEnvironment)
                resolve(returnValue)
              },
              error => {
                setIsInulaActEnvironment(previousActEnvironment)
                reject(error)
              },
            )
          },
        }
      } else {
        setIsInulaActEnvironment(previousActEnvironment)
        return actResult
      }
    } catch (error) {
      setIsInulaActEnvironment(previousActEnvironment)
      throw error
    }
  }
}

const act = withGlobalActEnvironment(inulaAct)

export default act
export {
  setIsInulaActEnvironment,
  getIsInulaActEnvironment,
}